package dts_tcp

import (
	"context"
	"errors"
	"fmt"
	"net"
	"sync"
	"sync/atomic"
	"time"
)

const (
	NotConnected ClientStatus = 1 // 未连接
	Connecting   ClientStatus = 2 // 正在连接
	Connected    ClientStatus = 3 // 已连接
	Disconnected ClientStatus = 4 // 连接断开
)

type ClientStatus byte

func (s ClientStatus) toInt32() int32 {
	return int32(s)
}

func (s ClientStatus) toPointInt32() *int32 {
	i := int32(s)
	return &i
}

func (s ClientStatus) IsConnected() bool {
	return s == Connected
}

func (s ClientStatus) String() string {
	switch s {
	case NotConnected:
		return "未连接"
	case Connecting:
		return "正在连接"
	case Connected:
		return "已连接"
	case Disconnected:
		return "连接断开"
	}
	return "未知状态"
}

type ClientConfig struct {
	*Config
	Id             int           // 设备id
	Name           string        // 设备名称
	Server         string        // dts服务器地址
	ChannelMapping map[byte]int  // 通道号与通道id对应关系
	Reconnect      bool          // 是否重连
	Timeout        time.Duration // 连接超时时间
}

type At struct {
	Connected  time.Time
	Disconnect time.Time
}

type Client struct {
	ctx           context.Context
	cancel        context.CancelFunc
	parentContext context.Context
	wait          sync.WaitGroup
	config        *ClientConfig // 配置
	addr          *net.TCPAddr
	conn          *net.TCPConn // 连接实例
	status        int32
	server        *Server // 连接模式位测温主机位客户端,平台为服务端生效
	Protocol      *Protocol
	At            At
}

func NewClient(ctx context.Context, config *ClientConfig) *Client {
	var (
		addr *net.TCPAddr
		err  error
	)

	if config.Precision == 0 {
		config.Precision = 0.5
	}

	addr, err = net.ResolveTCPAddr("tcp", config.Server)
	if err != nil {
		addr = &net.TCPAddr{IP: []byte{127, 0, 0, 1}, Port: 5050}
	}

	if config.Id == 0 {
		config.Id = int(addr.IP.To4()[3])
	}

	if config.Name == "" {
		config.Name = fmt.Sprintf("DTS%d:%s", config.Id, config.Server)
	}

	if config.Timeout == 0 {
		config.Timeout = time.Second * 5
	}

	debug = config.Debug

	client := &Client{
		parentContext: ctx,
		config:        config,
		addr:          addr,
		status:        int32(NotConnected),
	}
	return client
}

func (c *Client) Run() {
	// 正在连接
	c.setStatus(Connecting)

	PushLog(I, fmt.Sprintf("当前客户端<%s>连接DTS服务端<%s>...", c.config.Name, c.config.Server))

	conn, err := net.DialTimeout("tcp", c.addr.String(), c.config.Timeout)
	if err != nil {
		c.setStatus(NotConnected)
		PushLog(E, fmt.Sprintf("当前客户端<%s>连接DTS服务端<%s>失败: %s", c.config.Name, c.config.Server, err))
		if c.config.Reconnect {
			go c.Run()
		}
		return
	}

	c.conn = conn.(*net.TCPConn)

	err = c.conn.SetReadBuffer(1024 * 1024 * 2)
	if err != nil {
		c.setStatus(Disconnected)
		if c.config.Reconnect {
			go c.Run()
		}
		return
	}

	PushLog(I, fmt.Sprintf("当前客户端<%s>连接DTS服务端<%s>成功", c.config.Name, c.config.Server))

	c.setStatus(Connected)

	c.Protocol = NewProtocol(c, c.config.Config)

	c.Protocol.Read()
}

func (c *Client) Exec(cmd []byte) error {
	if !c.IsConnected() {
		return errors.New("not connected")
	}
	n, err := c.conn.Write(cmd)
	if err != nil {
		return err
	}
	if n != len(cmd) {
		return err
	}
	time.Sleep(time.Second)
	return nil
}

func (c *Client) ExecCmd(cmd []byte) error {
	if !c.IsConnected() {
		return errors.New("not connected")
	}
	time.Sleep(time.Second / 2)
	n, err := c.conn.Write(cmd)
	if err != nil {
		return err
	}
	if n != len(cmd) {
		return err
	}
	return nil
}

func (c *Client) setStatus(status ClientStatus) {
	atomic.StoreInt32(&c.status, int32(status))
	switch status {
	case Connected:
		c.At.Connected = time.Now()
	case Disconnected:
		c.At.Disconnect = time.Now()
	}
	LoadCallsFilterCodeRun(CodeClientChangeStatus, c)
}

func (c *Client) GetStatus() ClientStatus {
	status := atomic.LoadInt32(&c.status)
	return ClientStatus(status)
}

func (c *Client) IsConnected() bool {
	return c.GetStatus() == Connected
}
